/* Values.cs -- 
   Copyright (C) 2008  Casey Marshall.

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or (at
your option) any later version.

This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.  */


using System;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
using SexpSharp;

namespace SexpSharp.RPC
{
	public static class Values
	{
		public const string BoolName = "bool";
		public const string IntName = "int";
		public const string RealName = "real";
		public const string StringName = "string";
		public const string DataName = "data";
		public const string DateTimeName = "date/time";
		public const string ArrayName = "array";
		public const string StructName = "struct";
		public const string NilName = "nil";
		public const string DictName = "dict";
		public const string VectorName = "vector";

		public static IValue ExpressionToValue(Expression e)
		{
			if (e == null)
				throw new ArgumentNullException("");
			if (e is Atom)
				return new StringValue(e);
			List l = (List) e;
			if (l.Count < 1)
				throw new ArgumentException("lists must have at least one element");
			Atom name = l.First as Atom;
			if (name == null)
				throw new ArgumentException("first element must be an atom");

			switch (name.ToString())
			{
			case BoolName: return new BoolValue(e);
			case IntName: return new IntegerValue(e);
			case RealName: return new RealValue(e);
			case StringName: return new StringValue(e);
			case DataName: return new DataValue(e);
			case DateTimeName: return new DateTimeValue(e);
			case ArrayName: return new ArrayValue(e);
			case StructName: return new StructValue(e);
			case NilName: return new NullValue(e);
			case DictName: return new DictionaryValue(e);
			case VectorName: return new VectorValue(e);
			}

			throw new ArgumentException("invalid type token: " + name);
		}

		public static IValue[] ObjectsToValues(object[] o)
		{
			IValue[] ret = new IValue[o.Length];
			for (int i = 0; i < o.Length; i++)
				ret[i] = ObjectToValue(o[i]);
			return ret;
		}

		public static IValue ObjectToValue(object o)
		{
			if (o == null)
				return NullValue.Instance;
			if (o is IValue)
				return (IValue) o;
			if (o is UInt32 || o is Int32 || o is UInt16
				|| o is Int16 || o is Byte || o is SByte)
				return new IntegerValue(((IConvertible) o).ToUInt32(null));
			if (o is Boolean)
				return new BoolValue((bool) o);
			if (o is Decimal || o is Single || o is Double)
				return new RealValue(((IConvertible) o).ToDecimal(null));
			if (o is DateTime)
				return new DateTimeValue((DateTime) o);
			if (o is string)
				return new StringValue((string) o);
			if (o is byte[])
				return new DataValue((byte[]) o);
			if (o is short[] || o is ushort[] || o is int[] || o is uint[]
				|| o is long[] || o is ulong[])
			{
				System.Console.Error.WriteLine("{0} type is {1}", o, o.GetType());
				return new VectorValue((Array) o);
			}
			if (o is IEnumerable)
			{
				ArrayValue array = new ArrayValue();
				foreach (object elem in (IEnumerable) o)
					array.Value.Add(ObjectToValue(elem));
				return array;
			}

			if (o is IDictionary)
			{
				IDictionary<IValue, IValue> d = new Dictionary<IValue, IValue>();
				foreach (DictionaryEntry e in (IDictionary) o)
					d[ObjectToValue(e.Key)] = ObjectToValue(e.Value);
				return new DictionaryValue(d);
			}

			// Reflectmotize the object->struct
			StructValue s = new StructValue();
			Type t = o.GetType();
			FieldInfo[] fields = t.GetFields(BindingFlags.Public | BindingFlags.Instance);
			foreach (FieldInfo field in fields)
			{
				s.Value[field.Name] = ObjectToValue(field.GetValue(o));
			}
			PropertyInfo[] properties = t.GetProperties(BindingFlags.Public | BindingFlags.Instance);
			foreach (PropertyInfo prop in properties)
			{
				s.Value[prop.Name] = ObjectToValue(prop.GetValue(o, null));
			}
			return s;
		}

		public static object[] ValuesToObjects(IValue[] values, Type[] types)
		{
			if (values.Length != types.Length)
				throw new ArgumentException("argument list not the same size as types list");
			object[] ret = new object[values.Length];
			for (int i = 0; i < values.Length; i++)
				ret[i] = ValueToObject(values[i], types[i]);
			return ret;
		}

		public static T ValueToObject<T>(IValue value)
		{
			return (T) ValueToObject(value, typeof(T));
		}

		public static object ValueToObject(IValue value, Type target)
		{
			// If no type is specified, return the underlying type.
			if (target == null)
			{
				if (value is NullValue)
					return null;
				if (value is BoolValue)
					return ((BoolValue) value).Value;
				if (value is IntegerValue)
					return ((IntegerValue) value).Value;
				if (value is RealValue)
					return ((RealValue) value).Value;
				if (value is DateTimeValue)
					return ((DateTimeValue) value).Value;
				if (value is StringValue)
					return ((StringValue) value).Value;
				if (value is DataValue)
					return ((DataValue) value).Value;
				if (value is ArrayValue)
				{
					ArrayValue av = (ArrayValue) value;
					object[] ret = new object[av.Value.Count];
					for (int i = 0; i < av.Value.Count; i++)
						ret[i] = ValueToObject (av.Value[i], null);
					return ret;
				}
				if (value is StructValue)
				{
					StructValue sv = (StructValue) value;
					Hashtable ret = new Hashtable();
					foreach (KeyValuePair<string, IValue> e in sv.Value)
						ret[e.Key] = ValueToObject(e.Value, null);
					return ret;
				}
				if (value is VectorValue)
					return ((VectorValue) value).Value;
				if (value is DictionaryValue)
				{
					DictionaryValue dv = (DictionaryValue) value;
					Hashtable ret = new Hashtable();
					foreach (KeyValuePair<IValue, IValue> e in dv.Value)
						ret[ValueToObject(e.Key, null)] = ValueToObject(e.Value, null);
					return ret;
				}
			}

			if (value is NullValue)
			{
				if (!target.IsValueType
					|| (target.IsGenericType
						&& typeof(Nullable<>).Equals(target.GetGenericTypeDefinition())))
					return null;
				throw new NullReferenceException("type `" + target + "' can't be assigned a null reference");
			}

			if (value is BoolValue && target.IsAssignableFrom(typeof(Boolean)))
				return ((BoolValue) value).Value;

			if (value is IntegerValue)
			{
				uint u = ((IntegerValue) value).Value;
				if (target == typeof(uint))
					return u;
				if (target == typeof(int))
					return unchecked((int) u);
			}
			if (value is RealValue)
				return ((IConvertible) ((RealValue) value).Value).ToType(target, null);
			if (value is DateTimeValue && target.IsAssignableFrom(typeof(DateTime)))
				return ((DateTimeValue) value).Value;
			if (value is StringValue && target.IsAssignableFrom(typeof(String)))
				return ((StringValue) value).Value;
			if (value is DataValue && target.IsAssignableFrom(typeof(byte[])))
				return ((DataValue) value).Value;
			if (value is ArrayValue && target.IsArray)
			{
				Type etype = target.GetElementType();
				if (etype == typeof(object))
					etype = null;
				Array val = Array.CreateInstance(target.GetElementType(),
												 ((ArrayValue) value).Value.Count);
				int i = 0;
				foreach (IValue v in ((ArrayValue) value).Value)
					val.SetValue(ValueToObject(v, etype), i++);
				return val;
			}
			if (value is StructValue)
			{
				ConstructorInfo ci = target.GetConstructor(new Type[0]);
				if (ci == null)
					throw new RPCException("requested type `" + target
										   + "' does not define a zero-argument constructor");
				object o = ci.Invoke(new object[0]);
				FieldInfo[] fields = target.GetFields();
				foreach (FieldInfo field in fields)
				{
					IValue v = null;
					if (!((StructValue) value).Value.TryGetValue(field.Name, out v))
					{
						throw new RPCException("field `" + field.Name + "' in type `"
											   + target + "' was not specified in expression");
					}
					field.SetValue(o, ValueToObject(v, field.FieldType));
				}
				return o;
			}
			if (value is DictionaryValue)
			{
			}
			if (value is VectorValue)
			{
				VectorValue vv = (VectorValue) value;
				if (!typeof(Array).IsAssignableFrom (target))
					throw new RPCException("`" + target + "' is not an array type");
				Type etype = target.GetElementType();
				Array ret = Array.CreateInstance (etype, vv.Value.Length);
				for (int i = 0; i < vv.Value.Length; i++)
					ret.SetValue (((IConvertible) vv.Value.GetValue(i)).ToType(etype, null),
								  i);
				return ret;
			}

			throw new RPCException("failed to convert value " + value
								   + " to target type " + target);
		}
	}
}
